﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace VtcPositionDemo.Common.Utils
{
    class DateUtils
    {
		/// <summary> 日期格式 <c>[yyyy-MM-dd]</c> </summary>
		public static readonly DateUtils DATE = new DateUtils("yyyy-MM-dd");

		/// <summary> 日期格式 <c>[yyyyMMdd]</c> </summary>
		public static readonly DateUtils DATE_COMPACT = new DateUtils("yyyyMMdd");

		/// <summary> 日期格式 <c>[yyyy_MM_dd]</c> </summary>
		public static readonly DateUtils DATE_UNDERLINE = new DateUtils("yyyy_MM_dd");

		/// <summary> 时间格式 <c>[HH:mm:ss]</c> </summary>
		public static readonly DateUtils TIME = new DateUtils("HH:mm:ss");

		/// <summary> 时间格式 <c>[HHmmss]</c> </summary>
		public static readonly DateUtils TIME_COMPACT = new DateUtils("HHmmss");

		/// <summary> 时间格式 <c>[HH_mm_ss]</c> </summary>
		public static readonly DateUtils TIME_UNDERLINE = new DateUtils("HH_mm_ss");

		/// <summary> 时间格式 <c>[HH:mm:ss.fff]</c> </summary>
		public static readonly DateUtils TIME_MILLI = new DateUtils("HH:mm:ss.fff");

		/// <summary> 时间格式 <c>[HHmmssfff]</c> </summary>
		public static readonly DateUtils TIME_MILLI_COMPACT = new DateUtils("HHmmssfff");

		/// <summary> 时间格式 <c>[HH_mm_ss_fff]</c> </summary>
		public static readonly DateUtils TIME_MILLI_UNDERLINE = new DateUtils("HH_mm_ss_fff");

		/// <summary> 日期时间格式 <c>[yyyy-MM-dd HH:mm:ss]</c> </summary>
		public static readonly DateUtils DATE_TIME = new DateUtils("yyyy-MM-dd HH:mm:ss");

		/// <summary> 日期时间格式 <c>[yyyyMMddHHmmss]</c> </summary>
		public static readonly DateUtils DATE_TIME_COMPACT = new DateUtils("yyyyMMddHHmmss");

		/// <summary> 日期时间格式 <c>[yyyy_MM_dd_HH_mm_ss]</c> </summary>
		public static readonly DateUtils DATE_TIME_UNDERLINE = new DateUtils("yyyy_MM_dd_HH_mm_ss");

		/// <summary> 日期时间格式 <c>[yyyy-MM-dd HH:mm:ss.fff]</c> </summary>
		public static readonly DateUtils DATE_TIME_MILLI = new DateUtils("yyyy-MM-dd HH:mm:ss.fff");

		/// <summary> 日期时间格式 <c>[yyyyMMddHHmmssfff]</c> </summary>
		public static readonly DateUtils DATE_TIME_MILLI_COMPACT = new DateUtils("yyyyMMddHHmmssfff");

		/// <summary> 日期时间格式 <c>[yyyy_MM_dd_HH_mm_ss_fff]</c> </summary>
		public static readonly DateUtils DATE_TIME_MILLI_UNDERLINE = new DateUtils("yyyy_MM_dd_HH_mm_ss_fff");


		// ----------------------------------------------------------------------------------------------


		private const int DAYS_PER_WEEK = 7;

		private const long TICKS_PER_SEC = 10000000L;

		private const long TICKS_PER_MILLISEC = 10000L;

		private const long UNIX_EPOCH_TICKS = 621355968000000000L;

		private readonly string _pattern;

		private DateUtils() { }

		private DateUtils(string pattern)
		{
			_pattern = pattern;
		}


		// ----------------------------------------------------------------------------------------------


		/// <summary>
		/// Formats a date-time object using this formatter.
		/// </summary>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <returns>the formatted string, not null</returns>
		public string Now()
		{
			return Format(DateTime.Now);
		}

		/// <summary>
		/// Formats a date-time object using this formatter.
		/// </summary>
		/// <param name="datetime">the date-time object to format, not null</param>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <returns>the formatted string, not null</returns>
		public string Format(DateTime datetime)
		{
			return datetime.ToString(_pattern, DateTimeFormatInfo.InvariantInfo);
		}

		/// <summary>
		/// Formats a date-time object using this formatter.
		/// </summary>
		/// <param name="epochSecond">the epoch seconds to format, not null</param>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>the formatted string, not null</returns>
		public string FormatEpochSecond(long epochSecond)
		{
			return Format(OfEpochSecond(epochSecond));
		}

		/// <summary>
		/// Formats a date-time object using this formatter.
		/// </summary>
		/// <param name="epochMilli">the epoch milliseconds to format, not null</param>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>the formatted string, not null</returns>
		public string FormatEpochMilli(long epochMilli)
		{
			return Format(OfEpochMilli(epochMilli));
		}


		// ----------------------------------------------------------------------------------------------


		/// <summary>
		/// Fully parses the text producing an object of the specified type.
		/// </summary>
		/// <param name="datetime">the text to parse, not null</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <returns>the parsed date-time, not null</returns>
		public DateTime Parse(string datetime)
		{
			return DateTime.ParseExact(datetime, _pattern, CultureInfo.InvariantCulture);
		}

		/// <summary>
		/// Fully parses the text producing an object of the specified type.
		/// </summary>
		/// <param name="datetime">the text to parse, not null</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <returns>the parsed time-stamp</returns>
		public long ParseEpochSecond(string datetime)
		{
			return ToEpochSecond(Parse(datetime));
		}

		/// <summary>
		/// Fully parses the text producing an object of the specified type.
		/// </summary>
		/// <param name="datetime">the text to parse, not null</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <returns>the parsed time-stamp</returns>
		public long ParseEpochMilli(string datetime)
		{
			return ToEpochMilli(Parse(datetime));
		}


		// ----------------------------------------------------------------------------------------------


		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="years">the years to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddYears(string datetime, int years)
		{
			return Format(Parse(datetime).AddYears(years));
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="months">the months to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddMonths(string datetime, int months)
		{
			return Format(Parse(datetime).AddMonths(months));
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="weeks">the weeks to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddWeeks(string datetime, int weeks)
		{
			return AddDays(datetime, weeks * DAYS_PER_WEEK);
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="days">the days to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddDays(string datetime, int days)
		{
			return Format(Parse(datetime).AddDays(days));
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="hours">the hours to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddHours(string datetime, int hours)
		{
			return Format(Parse(datetime).AddHours(hours));
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="minutes">the minutes to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddMinutes(string datetime, int minutes)
		{
			return Format(Parse(datetime).AddMinutes(minutes));
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="seconds">the seconds to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddSeconds(string datetime, int seconds)
		{
			return Format(Parse(datetime).AddSeconds(seconds));
		}

		/// <summary>
		/// Returns a copy of this <tt>date-time</tt> with the specified number of years added.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="milliseconds">the milliseconds to add, may be negative</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception>
		/// <returns>based on this date-time with the years added, not null</returns>
		public string AddMilliseconds(string datetime, int milliseconds)
		{
			return Format(Parse(datetime).AddMilliseconds(milliseconds));
		}


		// ----------------------------------------------------------------------------------------------


		/// <summary>
		/// Checks if the year is a leap year, according to the ISO proleptic calendar system rules.
		/// This method applies the current rules for leap years across the whole time-line. 
		/// In general, a year is a leap year if it is divisible by four without remainder. 
		/// However, years divisible by 100, are not leap years, with the exception of years divisible by 400 which are.
		/// The calculation is proleptic - applying the same rules into the far future and far past. 
		/// This is historically inaccurate, but is correct for the ISO-8601 standard.
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <returns>true if the year is leap, false otherwise</returns>
		public bool IsLeapYear(string datetime)
		{
			return IsLeapYear(Parse(datetime));
		}

		/// <summary>
		/// Checks if this date-time is after the specified date-time.
		/// </summary>
		/// <param name="source">date-time of the String type</param>
		/// <param name="target">the date-time object to compare to</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <returns>true if this date-time is after the specified date-time</returns>
		public bool IsAfter(string source, DateTime target)
		{
			return Parse(source) > target;
		}

		/// <summary>
		/// Checks if this date-time is before the specified date-time.
		/// </summary>
		/// <param name="source">date-time of the String type</param>
		/// <param name="target">the date-time object to compare to</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <returns>true if this date-time is before the specified date-time</returns>
		public bool IsBefore(string source, DateTime target)
		{
			return Parse(source) < target;
		}

		/// <summary>
		/// Checks if this date-time is equal to the specified date-time.
		/// </summary>
		/// <param name="source">date-time of the String type</param>
		/// <param name="target">the date-time object to compare to</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <returns>true if this date-time is equal to the specified date-time</returns>
		public bool IsEqual(string source, DateTime target)
		{
			return Parse(source) == target;
		}

		/// <summary>
		/// Change the format of the date display
		/// </summary>
		/// <param name="datetime">date-time of the String type</param>
		/// <param name="pattern">the format of the date display</param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <returns></returns>
		public string Transform(string datetime, string pattern)
		{
			return Parse(datetime).ToString(pattern, DateTimeFormatInfo.InvariantInfo);
		}


		// ----------------------------------------------------------------------------------------------


		/// <summary>
		/// Checks if the year is a leap year, according to the ISO proleptic calendar system rules.
		/// This method applies the current rules for leap years across the whole time-line. 
		/// In general, a year is a leap year if it is divisible by four without remainder. 
		/// However, years divisible by 100, are not leap years, with the exception of years divisible by 400 which are.
		/// The calculation is proleptic - applying the same rules into the far future and far past. 
		/// This is historically inaccurate, but is correct for the ISO-8601 standard.
		/// </summary>
		/// <param name="datetime">the date-time object</param>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <returns>true if the year is leap, false otherwise</returns>
		public static bool IsLeapYear(DateTime datetime)
		{
			//return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0 && year % 3200 != 0);
			return DateTime.IsLeapYear(datetime.Year);
		}

		/// <summary>
		/// Convert the epoch seconds to date-time object.
		/// </summary>
		/// <param name="epochSecond">the epoch seconds to convert</param>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception> 
		/// <returns>the parsed date-time</returns>
		public static DateTime OfEpochSecond(long epochSecond)
		{
			// ticks = 621355968000000000L + epochSecond * 10000000L;
			var ticks = UNIX_EPOCH_TICKS + epochSecond * TICKS_PER_SEC;
			return new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
		}

		/// <summary>
		/// Convert the epoch milliseconds to date-time object.
		/// </summary>
		/// <param name="epochMilli">the epoch milliseconds to convert</param>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <exception cref="ArgumentException"></exception> 
		/// <returns>the parsed date-time</returns>
		public static DateTime OfEpochMilli(long epochMilli)
		{
			// ticks = 621355968000000000L + epochMilli * 10000L;
			var ticks = UNIX_EPOCH_TICKS + epochMilli * TICKS_PER_MILLISEC;
			return new DateTime(ticks, DateTimeKind.Utc).ToLocalTime();
		}

		/// <summary>
		/// Convert the date-time object to epoch seconds.
		/// </summary>
		/// <param name="datetime">the date-time object to convert, not null</param>
		/// <returns>the parsed time-stamp</returns>
		public static long ToEpochSecond(DateTime datetime)
		{
			// epochSecond = (ticks - 621355968000000000L) / 10000000L;
			return (datetime.ToUniversalTime().Ticks - UNIX_EPOCH_TICKS) / TICKS_PER_SEC;
		}

		/// <summary>
		/// Convert the date-time object to epoch milliseconds.
		/// </summary>
		/// <param name="datetime">the date-time object to convert, not null</param>
		/// <returns>the parsed time-stamp</returns>
		public static long ToEpochMilli(DateTime datetime)
		{
			// epochMilli = (ticks - 621355968000000000L) / 10000L;
			return (datetime.ToUniversalTime().Ticks - UNIX_EPOCH_TICKS) / TICKS_PER_MILLISEC;
		}

		/// <summary>
		/// Returns the current time in milliseconds. 
		/// Note thatwhile the unit of time of the return value is a millisecond,the granularity 
		/// of the value depends on the underlyingoperating system and may be larger. 
		/// For example, manyoperating systems measure time in units of tens ofmilliseconds.
		/// </summary>
		/// <returns>the difference, measured in milliseconds, betweenthe current time and midnight, January 1, 1970 UTC.</returns>
		public static long CurrentTimeMillis()
		{
			return ToEpochMilli(DateTime.Now);
		}

		/// <summary>
		/// Set the global time pattern for display.
		/// </summary>
		/// <param name="pattern">the time pattern</param>
		public static void SetGlobalPattern(string pattern = "yyyy-MM-dd HH:mm:ss")
		{
			if (DateTimeFormatInfo.CurrentInfo != null)
			{
				var type = DateTimeFormatInfo.CurrentInfo.GetType();
				if (type != null)
				{
					var field = type.GetField("generalLongTimePattern", BindingFlags.NonPublic | BindingFlags.Instance);
					if (field != null)
					{
						field.SetValue(DateTimeFormatInfo.CurrentInfo, pattern);
					}
				}
			}
		}

    }
}
